package cn.schoolwow.quickdao;

import cn.schoolwow.quickdao.annotation.IdStrategy;
import cn.schoolwow.quickdao.domain.DAO;
import cn.schoolwow.quickdao.domain.QuickDAOConfig;
import cn.schoolwow.quickdao.domain.database.dml.CheckStrategy;
import cn.schoolwow.quickdao.domain.database.dml.IDGenerator;
import cn.schoolwow.quickdao.domain.database.dql.condition.QueryColumnTypeMapping;
import cn.schoolwow.quickdao.domain.entity.*;
import cn.schoolwow.quickdao.domain.provider.*;
import cn.schoolwow.quickdao.module.entity.kit.TableDefiner;
import cn.schoolwow.quickdao.module.entity.kit.TableDefinerImpl;
import cn.schoolwow.quickdao.module.initial.service.InitialDAOCompositeBusiness;
import cn.schoolwow.quickdao.module.initial.service.InitialDataSourceCompositeBusiness;
import cn.schoolwow.quickdao.module.parent.handler.QuickDAOTryCatchFinallyHandler;
import cn.schoolwow.quickdao.module.parent.kit.DAOInvocationHandler;
import cn.schoolwow.quickflow.QuickFlowBuilder;

import javax.sql.DataSource;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class QuickDAO {
    //数据库提供者列表
    private static List<DatabaseProvider> databaseProviderList = new ArrayList<>(Arrays.asList(
            new H2DatabaseProvider(),
            new MariaDBDatabaseProvider(),
            new MySQLDatabaseProvider(),
            new OracleDatabaseProvider(),
            new PostgresDatabaseProvider(),
            new SQLiteDatabaseProvider(),
            new SQLServerDatabaseProvider()
    ));

    private QuickDAOConfig quickDAOConfig = new QuickDAOConfig();

    public static List<DatabaseProvider> getDatabaseProviderList(){
        return databaseProviderList;
    }

    /**
     * 添加新的数据库提供者
     */
    public static void addDatabaseProvider(DatabaseProvider databaseProvider) {
        databaseProviderList.add(databaseProvider);
    }

    /**
     * 新建实例
     */
    public static QuickDAO newInstance() {
        return new QuickDAO();
    }

    private QuickDAO() {
    }

    /**
     * 设置数据库连接池
     *
     * @param dataSource 数据库连接池
     */
    public QuickDAO dataSource(DataSource dataSource) {
        QuickFlowBuilder.quickFlow().startFlow(new InitialDataSourceCompositeBusiness())
                .putCurrentCompositeFlowData("databaseProviderList", databaseProviderList)
                .putInstanceData(quickDAOConfig)
                .putCurrentCompositeFlowData("dataSource", dataSource)
                .execute();
        return this;
    }

    /**
     * 待扫描实体类包名,支持嵌套扫描
     *
     * @param packageName 实体类所在包名
     */
    public QuickDAO packageName(String packageName) {
        quickDAOConfig.entityOption.packageNameMap.put(packageName, "");
        return this;
    }

    /**
     * 待扫描实体类包名,支持嵌套扫描
     *
     * @param packageName 实体类所在包名
     * @param prefix      表前缀
     */
    public QuickDAO packageName(String packageName, String prefix) {
        quickDAOConfig.entityOption.packageNameMap.put(packageName, prefix + "_");
        return this;
    }

    /**
     * 待扫描实体类包名,支持嵌套扫描
     *
     * @param entityClasses 实体类
     */
    public QuickDAO entity(Class... entityClasses) {
        for (Class entityClass : entityClasses) {
            quickDAOConfig.entityOption.entityClassMap.put(entityClass, "");
        }
        return this;
    }

    /**
     * 待扫描实体类包名,支持嵌套扫描
     *
     * @param entityClass 实体类
     * @param prefix      表前缀
     */
    public QuickDAO entity(Class entityClass, String prefix) {
        quickDAOConfig.entityOption.entityClassMap.put(entityClass, prefix);
        return this;
    }

    /**
     * 忽略包名
     *
     * @param ignorePackageName 扫描实体类时需要忽略的包
     */
    public QuickDAO ignorePackageName(String ignorePackageName) {
        if (quickDAOConfig.entityOption.ignorePackageNameList == null) {
            quickDAOConfig.entityOption.ignorePackageNameList = new ArrayList<>();
        }
        quickDAOConfig.entityOption.ignorePackageNameList.add(ignorePackageName);
        return this;
    }

    /**
     * 忽略该实体类
     *
     * @param ignoreClass 需要忽略的实体类
     */
    public QuickDAO ignoreClass(Class ignoreClass) {
        if (quickDAOConfig.entityOption.ignoreClassList == null) {
            quickDAOConfig.entityOption.ignoreClassList = new ArrayList<>();
        }
        quickDAOConfig.entityOption.ignoreClassList.add(ignoreClass);
        return this;
    }

    /**
     * 过滤实体类
     *
     * @param ignorePredicate 过滤实体类函数
     */
    public QuickDAO filter(Predicate<Class> ignorePredicate) {
        quickDAOConfig.entityOption.ignorePredicate = ignorePredicate;
        return this;
    }

    /**
     * 是否建立外键约束
     *
     * @param openForeignKey 指定是否建立外键约束
     */
    public QuickDAO foreignKey(boolean openForeignKey) {
        quickDAOConfig.databaseOption.openForeignKey = openForeignKey;
        return this;
    }

    /**
     * 是否自动建表
     *
     * @param autoCreateTable 指定是否自动建表,默认为true
     */
    public QuickDAO automaticCreateTable(boolean autoCreateTable) {
        quickDAOConfig.databaseOption.automaticCreateTable = autoCreateTable;
        return this;
    }

    /**
     * 是否自动更新字段
     *
     * @param autoCreateProperty 指定是否自动新增字段,默认为true
     */
    public QuickDAO automaticCreateProperty(boolean autoCreateProperty) {
        quickDAOConfig.databaseOption.automaticCreateProperty = autoCreateProperty;
        return this;
    }

    /**
     * 是否自动更新字段
     *
     * @param automaticUpdateProperty 指定是否自动更新字段,默认为false
     */
    public QuickDAO automaticUpdateProperty(boolean automaticUpdateProperty) {
        quickDAOConfig.databaseOption.automaticUpdateProperty = automaticUpdateProperty;
        return this;
    }

    /**
     * 指定仅在指定表列表内自动更新字段
     *
     * @param updateTableNames 需要自动更新字段的表名
     */
    public QuickDAO updateTableName(String... updateTableNames) {
        quickDAOConfig.databaseOption.updateTableNameList.addAll(Arrays.asList(updateTableNames));
        return this;
    }

    /**
     * 是否自动删除多余表和属性(和实体类对比)
     *
     * @param autoDeleteTableAndProperty 指定是否自动删除多余表和属性(和实体类对比),默认为false
     */
    public QuickDAO automaticDeleteTableAndProperty(boolean autoDeleteTableAndProperty) {
        quickDAOConfig.databaseOption.automaticDeleteTableAndProperty = autoDeleteTableAndProperty;
        return this;
    }

    /**
     * 指定全局Id生成策略
     *
     * @param idStrategy 全局id生成策略
     */
    public QuickDAO idStrategy(IdStrategy idStrategy) {
        quickDAOConfig.databaseOption.idStrategy = idStrategy;
        return this;
    }

    /**
     * 指定id生成器接口实例
     * <p><b>当id字段策略为IdGenerator起作用</b></p>
     *
     * @param idGenerator id生成器实例
     */
    public QuickDAO idGenerator(IDGenerator idGenerator) {
        quickDAOConfig.databaseOption.idGenerator = idGenerator;
        return this;
    }

    /**
     * 指定全局类型转换
     *
     * @param queryColumnTypeMapping 全局类型转换函数
     */
    public QuickDAO queryColumnTypeMapping(QueryColumnTypeMapping queryColumnTypeMapping) {
        quickDAOConfig.entityOption.queryColumnTypeMapping = queryColumnTypeMapping;
        return this;
    }

    /**
     * 指定单次批量插入个数
     *
     * @param perBatchCount 单次批量插入个数
     */
    public QuickDAO perBatchCount(int perBatchCount) {
        quickDAOConfig.databaseOption.perBatchCount = perBatchCount;
        return this;
    }

    /**
     * 插入时设置字段值
     *
     * @param insertColumnValueFunction 插入时设置字段值函数,参数为字段信息,返回值为该字段对应值,若为null则忽略该值
     */
    public QuickDAO insertColumnValueFunction(Function<Property, Object> insertColumnValueFunction) {
        quickDAOConfig.databaseOption.insertColumnValueFunction = insertColumnValueFunction;
        return this;
    }

    /**
     * 更新时设置字段值
     *
     * @param updateColumnValueFunction 更新时设置字段值函数,参数为字段信息,返回值为该字段对应值,若为null则忽略该值
     */
    public QuickDAO updateColumnValueFunction(Function<Property, Object> updateColumnValueFunction) {
        quickDAOConfig.databaseOption.updateColumnValueFunction = updateColumnValueFunction;
        return this;
    }

    /**
     * 指定虚拟表
     *
     * @param virtualTableNames 虚拟表名称
     */
    public QuickDAO virtualTableName(String... virtualTableNames) {
        quickDAOConfig.databaseOption.virtualTableNameList.addAll(Arrays.asList(virtualTableNames));
        return this;
    }

    /**
     * 设置是否记录SQL日志到数据库
     *
     * @param recordSqlLog 是否记录sql日志到数据
     */
    public QuickDAO recordSqlLog(boolean recordSqlLog) {
        quickDAOConfig.logRecordOption.recordSqlLog = recordSqlLog;
        return this;
    }

    /**
     * 设置是否打印流程日志
     *
     * @param recordFlowLog 是否打印流程日志
     */
    public QuickDAO recordFlowLog(boolean recordFlowLog) {
        quickDAOConfig.logRecordOption.recordFlowLog = recordFlowLog;
        return this;
    }

    /**
     * 设置全局检查约束策略
     *
     * @param checkStrategy 检查约束策略
     */
    public QuickDAO checkStrategy(CheckStrategy checkStrategy) {
        quickDAOConfig.databaseOption.checkStrategy = checkStrategy;
        return this;
    }

    /**
     * 自定义表和列
     */
    public TableDefiner define(Class clazz) {
        if (null == quickDAOConfig.databaseContext.dataSource) {
            throw new IllegalArgumentException("请先调用dataSource方法配置数据源!");
        }
        Entity entity = new Entity();
        entity.clazz = clazz;
        quickDAOConfig.entityOption.definerEntityMap.put(clazz.getName(), entity);
        return new TableDefinerImpl(entity, this);
    }

    /**
     * 自动建表之前执行语句
     */
    public QuickDAO beforeAutomaticCreate(String name, Consumer<DAO> daoConsumer) {
        if(quickDAOConfig.upgradeOption.beforeAutomaticCreate.containsKey(name)){
            throw new IllegalArgumentException("升级语句名称重复!名称:"+name);
        }
        quickDAOConfig.upgradeOption.beforeAutomaticCreate.put(name, daoConsumer);
        return this;
    }

    /**
     * 自动建表之后执行语句
     */
    public QuickDAO afterAutomaticCreate(String name, Consumer<DAO> daoConsumer) {
        if(quickDAOConfig.upgradeOption.afterAutomaticCreate.containsKey(name)){
            throw new IllegalArgumentException("升级语句名称重复!名称:"+name);
        }
        quickDAOConfig.upgradeOption.afterAutomaticCreate.put(name, daoConsumer);
        return this;
    }

    /**
     * 指定实体类监听器
     */
    public QuickDAO entityListener(EntityListener entityListener) {
        quickDAOConfig.entityOption.entityListener = entityListener;
        return this;
    }

    public DAO build() {
        if (null == quickDAOConfig.databaseContext.databaseProvider) {
            throw new IllegalArgumentException("请先调用dataSource方法配置数据源!");
        }

        quickDAOConfig.quickFlow = QuickFlowBuilder.newInstance()
                .printTrace(quickDAOConfig.logRecordOption.recordFlowLog)
                .putContextTemporaryInstanceData(quickDAOConfig)
                .putContextTemporaryInstanceData(quickDAOConfig.databaseContext.databaseProvider.getDatabaseType())
                .putContextTemporaryInstanceData(quickDAOConfig.databaseContext.databaseProvider, DatabaseProvider.class)
                .tryCatchFinallyHandler(new QuickDAOTryCatchFinallyHandler());

        DAOInvocationHandler daoInvocationHandler = new DAOInvocationHandler(quickDAOConfig.quickFlow, quickDAOConfig);
        DAO daoProxy = (DAO) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{DAO.class}, daoInvocationHandler);

        entity(SqlLog.class);
        entity(DatabaseUpgrade.class);
        quickDAOConfig.dao = daoProxy;
        //自动建表
        quickDAOConfig.quickFlow.startFlow(new InitialDAOCompositeBusiness())
                .putTemporaryData("dao", daoProxy)
                .execute();
        return daoProxy;
    }

}
